package proto

import (
	"bufio"
	"bytes"
	"encoding/binary"
	"fmt"
	"io"
)

const (
	ProtoVersion = 1
	CodeSuccess  = 0
	CodeFailed   = 1
	MsgSuccess   = "success"
	MsgVersion   = 1
	CID_OK       = 0
	CID_DEL      = 1
)
const (
	OpSingleSend              = 1
	OpSingleSendAck           = 2
	OpGroupSend               = 3
	OpGroupSendAck            = 4
	OpBuildConn               = 5
	OpOnlineUser              = 6
	OpOnlineUserAck           = 7
	OpKickUserOnline          = 8
	OpKickUserOffline         = 9
	OpPing                    = 10
	OpPong                    = 11
	OpAllUser                 = 12
	OpAllUserAck              = 13
	OpUserInfo                = 14
	OpUserInfoAck             = 15
	OpUserStat                = 16
	OpUserStatAck             = 17
	OpChangeAvatar            = 18
	OpChangeAvatarAck         = 19
	OpChangeAvatarNotify      = 20
	OpGroupChangeMember       = 21
	OpGroupChangeMemberAck    = 22
	OpGroupChangeMemberNotify = 23
	OpChangeSignInfo          = 24
	OpChangeSignInfoAck       = 25
	OpChangeSignInfoNotify    = 26
	OpDepartment              = 27
	OpDepartmentAck           = 28
	OpNormalGroupList         = 29
	OpNormalGroupListAck      = 30
	OpGroupInfoList           = 31
	OpGroupInfoListAck        = 32
	OpGroupCreate             = 33
	OpGroupCreateAck          = 34
	OpUnreadMsgCnt            = 35
	OpUnreadMsgCntAck         = 36
	OpGetMsgList              = 37
	OpGetMsgListAck           = 38
	OpGetLatestMsgId          = 39
	OpGetLatestMsgIdAck       = 40
	OpGetMsgById              = 41
	OpGetMsgByIdAck           = 42
	OpMsgReadAck              = 43
	OpMsgReadNotify           = 44
	OpGroupChangeAvatar       = 45
	OpGroupChangeAvatarAck    = 46
	OpGroupChangeAvatarNotify = 47
)

const (
	MarkLen          = 2
	VersionLen       = 2
	LengthLen        = 2
	OptionLen        = 2
	SeqLen           = 2
	TcpHeaderLen     = MarkLen + VersionLen + LengthLen + OptionLen + SeqLen
	LengthStartIndex = 4
	LengthStopIndex  = 6
)

var MarkContent = [2]byte{'v', 'w'}

type Msg struct {
	Mark    [2]byte
	Version int16
	Length  int16
	Op      int16
	Seq     int16
	Msg     []byte
}

func (p *Msg) Pack(w io.Writer) error {
	var err error
	err = binary.Write(w, binary.BigEndian, &p.Mark)
	if err != nil {
		return err
	}
	err = binary.Write(w, binary.BigEndian, &p.Version)
	if err != nil {
		return err
	}
	err = binary.Write(w, binary.BigEndian, &p.Length)
	if err != nil {
		return err
	}
	err = binary.Write(w, binary.BigEndian, &p.Op)
	if err != nil {
		return err
	}
	err = binary.Write(w, binary.BigEndian, &p.Seq)
	if err != nil {
		return err
	}
	err = binary.Write(w, binary.BigEndian, p.Msg)
	return err
}

func (p *Msg) Unpack(r io.Reader) error {
	var err error
	err = binary.Read(r, binary.BigEndian, &p.Mark)
	if err != nil {
		return err
	}
	err = binary.Read(r, binary.BigEndian, &p.Version)
	if err != nil {
		return err
	}
	err = binary.Read(r, binary.BigEndian, &p.Length)
	if err != nil {
		return err
	}
	err = binary.Read(r, binary.BigEndian, &p.Op)
	if err != nil {
		return err
	}
	err = binary.Read(r, binary.BigEndian, &p.Seq)
	if err != nil {
		return err
	}
	p.Msg = make([]byte, p.Length-TcpHeaderLen)
	err = binary.Read(r, binary.BigEndian, p.Msg)
	return err
}

func (p *Msg) String() string {
	return fmt.Sprintf("mark:%s version:%d length:%d msg:%s",
		p.Mark,
		p.Version,
		p.Length,
		p.Msg,
	)
}

func (p *Msg) GetPackageLength() int16 {
	p.Length = TcpHeaderLen + int16(len(p.Msg))
	return p.Length
}

func NewMsg(op int16, msg []byte, seq int16) *Msg {
	pack := &Msg{
		Mark:    MarkContent,
		Version: ProtoVersion,
		Op:      op,
		Msg:     msg,
		Seq:     seq,
	}
	pack.GetPackageLength()
	return pack
}

type IMResponse struct {
	Code int
	Data string
	Msg  string
}

func NewScanner(r io.Reader) *bufio.Scanner {
	s := bufio.NewScanner(r)
	s.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
		if !atEOF && data[0] == MarkContent[0] && data[1] == MarkContent[1] {
			if len(data) >= TcpHeaderLen {
				packSumLength := int16(0)
				reader := bytes.NewReader((data[LengthStartIndex:LengthStopIndex]))
				binary.Read(reader, binary.BigEndian, &packSumLength)
				if int(packSumLength) <= len(data) {
					return int(packSumLength), data[:packSumLength], nil
				}
			}
		}
		return
	})
	return s
}

var _op_str = []string{
	OpSingleSend:              "OpSingleSend",
	OpSingleSendAck:           "OpSingleSendAck",
	OpGroupSend:               "OpGroupSend",
	OpGroupSendAck:            "OpGroupSendAck",
	OpBuildConn:               "OpBuildConn",
	OpOnlineUser:              "OpOnlineUser",
	OpOnlineUserAck:           "OpOnlineUserAck",
	OpKickUserOnline:          "OpKickUserOnline",
	OpKickUserOffline:         "OpKickUserOffline",
	OpPing:                    "OpPing",
	OpPong:                    "OpPong",
	OpAllUser:                 "OpAllUser",
	OpAllUserAck:              "OpAllUserAck",
	OpUserInfo:                "OpUserInfo",
	OpUserInfoAck:             "OpUserInfoAck",
	OpUserStat:                "OpUserStat",
	OpUserStatAck:             "OpUserStatAck",
	OpChangeAvatar:            "OpChangeAvatar",
	OpChangeAvatarAck:         "OpChangeAvatarAck",
	OpChangeAvatarNotify:      "OpChangeAvatarNotify",
	OpGroupChangeMember:       "OpGroupChangeMember",
	OpGroupChangeMemberAck:    "OpGroupChangeMemberAck",
	OpGroupChangeMemberNotify: "OpGroupChangeMemberNotify",
	OpChangeSignInfo:          "OpChangeSignInfo",
	OpChangeSignInfoAck:       "OpChangeSignInfoAck",
	OpChangeSignInfoNotify:    "OpChangeSignInfoNotify",
	OpDepartment:              "OpDepartment",
	OpDepartmentAck:           "OpDepartmentAck",
	OpNormalGroupList:         "OpNormalGroupList",
	OpNormalGroupListAck:      "OpNormalGroupListAck",
	OpGroupInfoList:           "OpGroupInfoList",
	OpGroupInfoListAck:        "OpGroupInfoListAck",
	OpGroupCreate:             "OpGroupCreate",
	OpGroupCreateAck:          "OpGroupCreateAck",
	OpUnreadMsgCnt:            "OpUnreadMsgCnt",
	OpUnreadMsgCntAck:         "OpUnreadMsgCntAck",
	OpGetMsgList:              "OpGetMsgList",
	OpGetMsgListAck:           "OpGetMsgListAck",
	OpGetLatestMsgId:          "OpGetLatestMsgId",
	OpGetLatestMsgIdAck:       "OpGetLatestMsgIdAck",
	OpGetMsgById:              "OpGetMsgById",
	OpGetMsgByIdAck:           "OpGetMsgByIdAck",
	OpMsgReadAck:              "OpMsgReadAck",
	OpMsgReadNotify:           "OpMsgReadNotify",
	OpGroupChangeAvatar:       "OpGroupChangeAvatar",
	OpGroupChangeAvatarAck:    "OpGroupChangeAvatarAck",
	OpGroupChangeAvatarNotify: "OpGroupChangeAvatarNotify",
}

func GetString(i int16) string {
	return _op_str[i]
}
